﻿// Memory.cs
// This program contains a class simulates memory virtually.
// It is used for both RAM and Registers

using System;
using System.Linq;

namespace armsim
{

    /// <summary>
    /// Simulates virtual memory for both ram and registers.
    /// Provides contants that correspond to the locations of each register
    /// </summary>
    public class VirtualMemory
    {
        public const uint R0 = 0;
        public const uint R1 = 4;
        public const uint R2 = 8;
        public const uint R3 = 12;
        public const uint R4 = 16;
        public const uint R5 = 20;
        public const uint R6 = 24;
        public const uint R7 = 28;
        public const uint R8 = 32;
        public const uint R9 = 36;
        public const uint R10 = 40;
        public const uint R11 = 44;

        public const uint R12 = 48, FP = 48;
        public const uint R13 = 52, SP = 52;
        public const uint R14 = 56, LR = 56;
        public const uint R15 = 60, PC = 60;
        
        public const uint CPSR = 64;
   

        // RAM Bytes

        /// <summary>
        /// Converts the register to its assembly representation
        /// </summary>
        /// <param name="regNum">The ARM Code for the register to Convert</param>
        /// <param name="useAliases">Uses the aliases instead of the r prefix with a number</param>
        /// <returns>the assembly code</returns>
        public static String RegToAssembly(uint regNum, Boolean useAliases = true)
        {
            if (useAliases)
            {
                switch (regNum)
                {
                    case 12u:
                        return "FP";
                    case 13u:
                        return "SP";
                    case 14u:
                        return "LR";
                    case 15u:
                        return "PC";
                    default:
                        break;
                }
            }

            return String.Format("R{0}", regNum);
        }

        /// <summary>
        /// Given a register number, returns the memory in that register
        /// </summary>
        /// <param name="regNum">the register number to retrieve data from</param>
        /// <returns>the register's contents</returns>
        public static uint MapRegNoToRegMem(uint regNum)
        {
            switch (regNum)
            {
                case 0u:
                    return R0;
                case 1u:
                    return R1;
                case 2u:
                    return R2;
                case 3u:
                    return R3;
                case 4u:
                    return R4;
                case 5u:
                    return R5;
                case 6u:
                    return R6;
                case 7u:
                    return R7;
                case 8u:
                    return R8;
                case 9u:
                    return R9;
                case 10u:
                    return R10;
                case 11u:
                    return R11;
                case 12u:
                    return R12;
                case 13u:
                    return R13;
                case 14u:
                    return R14;
                case 15u:
                    return R15;
                default:
                    throw new Exception("No such register exists!!!");
            }

        }

        /// <summary>
        /// The Random Access Memory
        /// </summary>
        protected Byte[] ByteArray { get; set; }

        /// <summary>
        /// Accessor Method
        /// </summary>
        /// <returns>the number of bytes in the ByteArray (the length)</returns>
        public int GetNumBytes()
        {
            return ByteArray.Length;
        }
        
        // Constructor

        /// <summary>
        /// Initialized a RAM object.
        /// </summary>
        /// <param name="memorySize">The maximum memory size for the ram. 
        /// Should be no larger than 1 MB. 
        /// A default value is provided
        /// </param>
        public VirtualMemory(uint memorySize = 32768U)
        {
            ByteArray = new Byte[memorySize];
        }

        // Readers

        /// <summary>
        /// Reads a byte from a specific address in memory. 
        /// Throws an exception if the reading is out of memory bounds
        /// </summary>
        /// <param name="address">location in ByteArray to read from</param>
        /// <returns>the 8 bit value from the specified location in RAM memory</returns>
        public Byte ReadByte(uint address)
        {
            if (address >= ByteArray.Length)
            {
                throw new Exception("In method ReadByte(): Address out of bounds of the array");
            }

            return ByteArray[address];
        }

        /// <summary>
        /// Reads a half word beginning at a specific memory address.
        /// Throws an exception if the reading is out of bounds or the address is not divisible by 2
        /// </summary>
        /// <param name="address">location in ByteArray to begin reading from</param>
        /// <returns>the 16 bit value from the specified location in RAM memory</returns>
        public ushort ReadHalfWord(uint address)
        {
            if (address % 2 != 0)
            {
                throw new Exception();
            }

            return BitConverter.ToUInt16(new [] { ReadByte(address), ReadByte(address + 1) }, 0);


        }

        /// <summary>
        /// Reads a Word from a specific memory address.
        /// Throws an exception if the reading out of bound or the address is not divisible by 4
        /// </summary>
        /// <param name="address">location in ByteArray to begin reading from</param>
        /// <returns>the 32 bit value from the specified location in RAM memory</returns>
        public virtual uint ReadWord(uint address)
        {
            if (address % 4 != 0)
            {
                throw new Exception();
            }

            return BitConverter.ToUInt32(new [] { ReadByte(address), ReadByte(address + 1), ReadByte(address + 2), ReadByte(address + 3) }, 0);

        }
 
        // Writers

        /// <summary>
        /// Writes a byte of data to a specific RAM memory address.
        /// Throws an exception if the address index is out of bounds.
        /// </summary>
        /// <param name="address">location in the ByteArray to write to</param>
        /// <param name="dataValue">the 8 bit (1 byte) value to be written</param>
        public void WriteByte(uint address, Byte dataValue)
        {
            if (address >= ByteArray.Length)
            {
                throw new Exception("In method WriteByte(): Address out of bounds of the array");
            }

            ByteArray[address] = dataValue;
        }

        /// <summary>
        /// Writes a half word of data beginning at a specific RAM memory address.
        /// Throws an exception if the address index is out of bounds or if the
        /// address is not divisible by 2
        /// </summary>
        /// <param name="address">location in the ByteArray to begin writing to</param>
        /// <param name="dataValues">the 16 bit (2 byte) value to be written</param>
        public void WriteHalfWord(uint address, ushort dataValues)
        {
            if (address % 2 != 0)
            {
                throw new Exception();
            }

            var bytes = BitConverter.GetBytes(dataValues);

            WriteByte(address, bytes[0]);
            WriteByte(address + 1, bytes[1]);

        }

        /// <summary>
        /// Writes a word of data beginning at a specifc RAM memory address.
        /// Throws an exception if the address index is out of bounds or if
        /// the address is not divisible by 4
        /// </summary>
        /// <param name="address">location in the ByteArray to begin writing to</param>
        /// <param name="dataValues">the 32 bit (4 byte) value to be written</param>
        public void WriteWord(uint address, uint dataValues)
        {
            if (address % 4 != 0)
            {
                throw new Exception();
            }
            var bytes = BitConverter.GetBytes(dataValues);

            WriteByte(address, bytes[0]);
            WriteByte(address + 1, bytes[1]);
            WriteByte(address + 2, bytes[2]);
            WriteByte(address + 3, bytes[3]);

        }

        // Bits and Flags

        /// <summary>
        /// Determines whether a specific bit of a word at a specified address is 0 or 1.
        /// Throws an exception if the address index is out of bounds.
        /// </summary>
        /// <param name="address">location of the word to test in the ByteArray</param>
        /// <param name="bit">location of the bit to test in the word</param>
        /// <returns>True if 1 (set). Otherwise, false.</returns>
        public Boolean TestFlag(uint address, int bit)
        {
            var word = ReadWord(address);
            var result = (word & (1 << bit)) != 0;
            return result;
        } 

        /// <summary>
        /// Sets a specific bit of a word at a specified address in the ByteArray to 1 or 0
        /// </summary>
        /// <param name="address">lcoation of the word in the ByteArray</param>
        /// <param name="bit">location of the bit to set in the word</param>
        /// <param name="flag">Either true for 1 or false for 0</param>
        public void SetFlag(uint address, int bit, Boolean flag)
        {

            var word = ReadWord(address);

            if (flag)
            {
                word |= (1U << bit);
            }
            else
            {
                word &= ~(1U << bit);
            }

            WriteWord(address, word);

        }
        
        /// <summary>
        /// Utility method that collects a range of bits from a Word (uint)
        /// </summary>
        /// <param name="word">The word to extract bits from</param>
        /// <param name="startbit">The starting bit (included)</param>
        /// <param name="endBit">The ending bit (included)</param>
        /// <returns>A Word containing only the bits in the range</returns>
        public static uint ExtractBits(uint word, int startbit, int endBit)
        {

            var andBit = 0U;

            for (var i = startbit; i <= endBit; ++i )
            {
                andBit = andBit | (1U << i);
            }

            var result = (andBit & word) >> startbit;

            return result;
        }

        /// <summary>
        /// Calculates the checksum for the RAM memory
        /// </summary>
        /// <returns>the checksum</returns>
        public int CheckSum()
        {
            return ByteArray.Select((t, address) => t ^ address).Sum();

        }

        /// <summary>
        /// Sets all the bytes in the memory to 0
        /// </summary>
        public void ResetAll()
        {
            Array.Clear(ByteArray, 0, ByteArray.Length);
        }

    }
}
